package club.jdiy.dev.zlt.service.impl;

import club.jdiy.core.base.JDiyBaseService;
import club.jdiy.core.ex.JDiyException;
import club.jdiy.core.ex.JDiyFormException;
import club.jdiy.core.sql.ColumnInfo;
import club.jdiy.core.sql.TableInfo;
import club.jdiy.dev.dao.JDiyUiDao;
import club.jdiy.dev.entity.JDiyUi;
import club.jdiy.dev.helper.OptsHelper;
import club.jdiy.dev.types.*;
import club.jdiy.dev.meta.InputMeta;
import club.jdiy.dev.meta.FormUiMeta;
import club.jdiy.dev.zlt.service.JDevInPageService;
import club.jdiy.admin.util.IdUtil;
import club.jdiy.utils.JsonUtils;
import club.jdiy.utils.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.regex.Pattern;


@SuppressWarnings("unused")
@Service
public class JDevInPageServiceImpl extends JDiyBaseService<JDiyUi, JDiyUiDao, String>
        implements JDevInPageService {

    @Override
    @Transactional
    public JDiyUi logicSave(JDiyUi vo) {
        JDiyUi po = dao.findById(vo.getId()).orElseGet(() -> {
            JDiyUi po1 = new JDiyUi();
            po1.setType(vo.getType());
            po1.setId(IdUtil.newId());
            po1.setBindType(vo.getBindType());
            if ("table".equals(vo.getBindType())) {
                po1.setMainTable(vo.getMainTable());
            } else {
                po1.setMainEntity(vo.getMainEntity());
                po1.setMainTable(context.getDao().getEntityMeta(vo.getMainEntity()).getTableName());
            }
            return po1;
        });
        po.setName(vo.getName());


        //tds:
        FormUiMeta fum = new FormUiMeta();
        Collection<ColumnInfo> columnInfos = context.getDao().getTableInfo(po.getMainTable()).getColumns().values();
        int i = 0;
        List<InputMeta> im = new ArrayList<>(columnInfos.size());
        for (ColumnInfo fv : columnInfos) {
            if (fv.isPrimaryKey()) continue;
            InputMeta c1 = new InputMeta();
            c1.setId(IdUtil.newId());
            c1.setField(fv.getName());
            String label = fv.getName();
            if (StringUtils.hasText(fv.getComment())) {
                label = fv.getComment();
                if (label.length() > 8) label = label.substring(0, 8);
                if (label.contains("(")) label = label.substring(0, label.indexOf("("));
                if ("".equals(label.trim())) label = fv.getName();
            }
            c1.setLabel(label);
            switch (fv.getBaseType()) {
                case MEDIUMTEXT:
                case LONGTEXT:
                case CLOB:
                    c1.setType(InputTpl.ueditor);
                    break;
                case BIT:
                    c1.setType(InputTpl.switcher);
                    c1.setOnt("是|否");
                    c1.setOnv("1|0");
                    break;
                case TINYINT:
                case SMALLINT:
                case MEDIUMINT:
                case INT:
                case BIGINT:
                    c1.setType(InputTpl.input);
                    c1.setFormat(FormatTpl.integer);
                    break;
                case FLOAT:
                case DOUBLE:
                case DECIMAL:
                    c1.setType(InputTpl.input);
                    c1.setFormat(FormatTpl.number);
                    break;
                case DATE:
                    c1.setType(InputTpl.input);
                    c1.setFormat(FormatTpl.date);
                    break;
                case TIME:
                    c1.setType(InputTpl.input);
                    c1.setFormat(FormatTpl.time);
                    break;
                case DATETIME:
                case TIMESTAMP:
                    c1.setType(InputTpl.input);
                    c1.setFormat(FormatTpl.datetime);
                    break;
                default:
                    c1.setType(InputTpl.input);
                    c1.setFormat(FormatTpl.text);
            }
            if (c1.getType() == InputTpl.ueditor) {
                i += 3;
                c1.setLayout(FormLayoutTpl.col12);
                c1.setRow(i / 2);
                c1.setCol(0);
                im.add(c1);
                i += 3;
            } else {
                c1.setLayout(FormLayoutTpl.col6);
                c1.setRow(i / 2);
                c1.setCol(i % 2);
                im.add(c1);
                i++;
            }
        }
        fum.setInputs(im.toArray(new InputMeta[0]));

        po.setMetaData(JsonUtils.stringify(fum));
        return dao.save(po);
    }

    private static final Pattern ont_onv = Pattern.compile("^[^|]+\\|[^|]+$");
    private static final Pattern h_item = Pattern.compile("^([^:\n]+:[^:\n]+)(\n([^:\n]+:[^:\n]+))*$");

    @Override
    @Transactional
    public void saveDesign(String uid, FormUiMeta uiMeta) {
        JDiyUi pg = dao.findById(uid).orElseThrow(
                () -> new JDiyFormException("表单界面信息不存在或被删除(uiId=" + uid + ")", "")
        );
        if (uiMeta.isAjaxSave()) {
            if (StringUtils.isBlank(uiMeta.getAjaxUrl()))
                throw new JDiyException("当前表单提交保存方式为“指定ajax地址”，但您还未配置提交地址，请在右侧全局设置中配置。");
            uiMeta.setAjaxUrl(uiMeta.getAjaxUrl().trim());

        } else {
            if (uiMeta.getPkTpl() == null)
                throw new JDiyException("未配置主键生成策略，请在页面右侧边栏上方点击“全局设置”选项卡，然后配置");
            else if (uiMeta.getPkTpl() == PkTpl.pres && StringUtils.isEmpty(uiMeta.getPkPre()))
                throw new JDiyException("当前表单配置的主键生成策略为[自定义前缀]，但您还没有设置前缀内容．请在页面右侧边栏上方点击“全局设置”选项卡，然后配置");
        }
        if (uiMeta.getHiddenParams() != null && !"".equals(uiMeta.getHiddenParams().trim())) {
            if (!h_item.matcher(uiMeta.getHiddenParams().trim()).matches()) {
                throw new JDiyException("隐藏更新格式输入不正确，必须是“名称:值”的格式一行填一个.请在右侧栏“全局设置”中更改");
            }
        }
        if (StringUtils.isEmpty(uiMeta.getPageHandler())) uiMeta.setPageHandler(null);

        TableInfo tableInfo = context.getDao().getTableInfo(pg.getMainTable());

        Map<String, String> repeatSet = new HashMap<>();
        String objId;
        for (InputMeta input : uiMeta.getInputs()) {
            objId = input.getId();
            if (InputTpl.checkbox == input.getType() && (ManyTpl.ManyToMany == input.getManyType() || ManyTpl.OneToMany == input.getManyType())) {
                input.setItems(null);
                input.setOptQry(null);
                TableInfo manyTableInfo = context.getDao().getTableInfo(input.getManyTable());
                if (Objects.equals(manyTableInfo.getPrimaryKey(), input.getOptValField()) && ManyTpl.OneToMany == input.getManyType())
                    throw new JDiyFormException("当为[一对多外键反向更新]时，数据来源表的[外键字段]不能是该表的主键id", objId);
                if (ManyTpl.ManyToMany == input.getManyType()) {
                    if (StringUtils.isEmpty(input.getManyTable()))
                        throw new JDiyFormException("选中的控件为多对多关联更新，请先选择多对多的关联关系表", objId);
                    if (StringUtils.isEmpty(input.getManyField0()))
                        throw new JDiyFormException("选中的控件为多对多关联更新，请先选择关系表的正向关联字段", objId);
                    if (StringUtils.isEmpty(input.getManyField1()))
                        throw new JDiyFormException("选中的控件为多对多关联更新，请先选择关系表的反向关联字段", objId);
                    if (input.getManyField1().equals(input.getManyField0()))
                        throw new JDiyFormException("选中的控件为多对多关联更新，正向和反向关联字段不能是同一个。", objId);
                }
                if (input.getOptType() == null) throw new JDiyException("请选择控件的动态条目数据来源．");
                else if (input.getOptType() != OptTpl.table && input.getOptType() != OptTpl.entity)
                    throw new JDiyFormException("关联更新为＂一对多/多对多＂时，动态条目的数据来源只能是数据库表或JPA实体.", objId);
                if (StringUtils.isEmpty(input.getOptTxtField()))
                    throw new JDiyFormException("选中的控件未配置动态选项显示字段名", objId);
                if (StringUtils.isEmpty(input.getOptValField())) throw new JDiyFormException("选中的控件未配置动态选项外键字段", objId);
                //todo 验证外键表／实体附加条件正确性
            } else {
                input.setManyType(null);
                if (StringUtils.isEmpty(input.getField())) {
                    throw new JDiyFormException("选中的控件未配置[绑定字段]", objId);
                }
                if (Objects.equals(tableInfo.getPrimaryKey(), input.getField()))
                    throw new JDiyFormException("主键[" + tableInfo.getPrimaryKey() + "]不能被控件绑定。", objId);
                if (repeatSet.get(input.getField()) != null && FormatTpl.pwdAgain != input.getFormat())
                    throw new JDiyFormException("一个字段只能被一个控件绑定．请检查选中控件的字段" + input.getField() + "绑定情况", objId);
                if (FormatTpl.pwdAgain != input.getFormat()) repeatSet.put(input.getField(), objId);
            }
            try {
                this.getClass().getDeclaredMethod(input.getType() + "_fill", InputMeta.class)
                        .invoke(this, input);
                if (input.getType() == InputTpl.file) {
                    if (Boolean.TRUE.equals(input.getNoStore())) {
                        if (!tableInfo.getColumns().containsKey(input.getField())) {
                            throw new JDiyFormException("选中的控件持久化方式为“仅存url到绑定字段”，此模式下绑定字段不能为[自定义].", objId);
                        }
                    }
                }
            } catch (InvocationTargetException ex) {
                if (ex.getTargetException() instanceof JDiyException) throw (JDiyException) ex.getTargetException();
                else throw new JDiyException(ex.getTargetException());
            } catch (Exception ne) {
                throw new JDiyException(ne.getMessage());
            }
        }
        if (uiMeta.getHiddenParams() != null && uiMeta.getHiddenParams().contains(":")) {
            for (String ss : uiMeta.getHiddenParams().split("\\n")) {
                String[] it = ss.trim().split(":");
                if (it.length > 1) {
                    objId = repeatSet.get(it[0].trim());
                    if (Objects.equals(tableInfo.getPrimaryKey(), it[0].trim()))
                        throw new JDiyException("主键字段[" + tableInfo.getPrimaryKey() + "]不能添加为隐藏域。请在右侧栏“全局设置-添加隐藏域”中去除。");
                    if (objId != null) {
                        throw new JDiyFormException("字段名[" + it[0] + "]已被当前控件绑定，不能再设为隐藏域，请在右侧栏“全局设置-添加隐藏域”中去除。", objId);
                    }
                }
            }
        }

        pg.setMetaData(JsonUtils.stringify(uiMeta));
        dao.save(pg);
    }

    private void __set_opts(InputMeta vo) {
        OptsHelper.designValidate(vo, vo.getId());
        //todo　附加查询条件，和　自定义ＳＱＬ的执行验证
    }

    private void file_fill(InputMeta vo) {
        if (FileTypeTpl.image == vo.getFileType()) {
            if (RoomTpl.none != vo.getRoom()) {
                if (vo.getRoomW() == 0 && vo.getRoomH() == 0)
                    throw new JDiyFormException("选中的上传控件已设置图片缩放，但缩放目标宽和高不能都是0，请在属性面板中修改后提交。", vo.getId());
            }
        }
    }

    private void ueditor_fill(InputMeta vo) {
    }
    private void colorbox_fill(InputMeta vo) {
    }

    private void radio_fill(InputMeta vo) {
        __set_opts(vo);
    }

    private void checkbox_fill(InputMeta vo) {
        __set_opts(vo);
    }

    private void select_fill(InputMeta vo) {
        __set_opts(vo);
    }

    private void treeSelect_fill(InputMeta vo) {
        if (StringUtils.isEmpty(vo.getTreeId()))
            throw new JDiyFormException("选中的树形下拉菜单还没有选择[数据来源]，请在属性面板中修改后提交。", vo.getId());
    }

    private void lookup_fill(InputMeta vo) {
        if (StringUtils.isEmpty(vo.getLookupId()))
            throw new JDiyFormException("选中的控件为查找带回，但您还没有配置查找界面。", vo.getId());

        if (vo.getLookupWidth() == null) vo.setLookupWidth(640);
        else if (vo.getLookupWidth() < 200) vo.setLookupWidth(200);

        if (vo.getLookupHeight() == null) vo.setLookupHeight(480);
        else if (vo.getLookupWidth() < 100) vo.setLookupWidth(100);
        if (StringUtils.isEmpty(vo.getLookupField()))
            throw new JDiyFormException("选中的控件为查找带回，您还没有配置带回的显示字段。", vo.getId());
        if (vo.getLookupMulti()) {
            if (vo.getLookupType() == null) throw new JDiyFormException("选中的控件为查找带回(多选)，您还没有选择关联更新方式。", vo.getId());
            else if (vo.getLookupType() == ManyTpl.OneToMany) {
                vo.setLookupJoinTable(null);
                vo.setLookupJoinField0(null);
                vo.setLookupJoinField1(null);
                if (StringUtils.isEmpty(vo.getLookupJoinField()))
                    throw new JDiyFormException("选中的控件为查找带回，一对多外键反向更新，您还没有选择要更新的外键字段。", vo.getId());
            } else if (vo.getLookupType() == ManyTpl.ManyToMany) {
                vo.setLookupJoinField(null);
                if (StringUtils.isEmpty(vo.getLookupJoinTable()))
                    throw new JDiyFormException("选中的控件为查找带回(多对多关系表更新)，请先选择关联关系表", vo.getId());
                if (StringUtils.isEmpty(vo.getLookupJoinField0()))
                    throw new JDiyFormException("选中的控件为查找带回(多对多关系表更新)，请先选择关系表的正向关联字段", vo.getId());
                if (StringUtils.isEmpty(vo.getLookupJoinField1()))
                    throw new JDiyFormException("选中的控件为查找带回(多对多关系表更新)，请先选择关系表的反向关联字段", vo.getId());
                if (vo.getLookupJoinField0().equals(vo.getLookupJoinField1()))
                    throw new JDiyFormException("选中的控件为查找带回(多对多关系表更新)，正向和反向关联字段不能是同一个。", vo.getId());
            }
        } else {
            vo.setLookupType(null);
            vo.setLookupJoinTable(null);
            vo.setLookupJoinField0(null);
            vo.setLookupJoinField1(null);
        }
    }

    private void switcher_fill(InputMeta vo) {
        if (!StringUtils.isEmpty(vo.getOnt())) {
            if (!ont_onv.matcher(vo.getOnt().trim()).matches())
                throw new JDiyFormException("选中的控件开关文字需为“A|B”的格式，如： 开|关　打开|关闭　ON|OFF　启用|停用 等等", vo.getId());
        } else {
            vo.setOnt("是|否");
        }

        if (!StringUtils.isEmpty(vo.getOnv())) {
            if (!ont_onv.matcher(vo.getOnv().trim()).matches())
                throw new JDiyFormException("选中的控件开关文字需为“A|B”的格式，如： 开|关　打开|关闭　ON|OFF　启用|停用 等等", vo.getId());
        } else {
            vo.setOnv("1|0");
        }
    }

    private void textarea_fill(InputMeta vo) {
    }

    private void input_fill(InputMeta vo) {
        if (FormatTpl.joinTable == vo.getFormat()) {
            if (StringUtils.isEmpty(vo.getJoinTable()))
                throw new JDiyFormException("选中的控件为外键反查，但您还没有选择外键表。请在属性面板中修改后提交。", vo.getId());
            if (StringUtils.isEmpty(vo.getJoinField()))
                throw new JDiyFormException("选中的控件为外键反查，但您还没有选择查询字段。请在属性面板中修改后提交。", vo.getId());
        } else if (FormatTpl.joinEntity == vo.getFormat()) {
            if (StringUtils.isEmpty(vo.getJoinTable()))
                throw new JDiyFormException("选中的控件为外键反查，但您还没有选择外键实体。请在属性面板中修改后提交。", vo.getId());
            if (StringUtils.isEmpty(vo.getJoinField()))
                throw new JDiyFormException("选中的控件为外键反查，但您还没有选择查询字段。请在属性面板中修改后提交。", vo.getId());
        } else if (FormatTpl.pwd == vo.getFormat()) {
            if (StringUtils.isEmpty(vo.getPwdType()))
                throw new JDiyFormException("选中控件输入类型为[密码框]，但您还未选择加密方式。", vo.getId());
        }
    }
}
